/*
 * @(#)Calculator.java 7/11/2006
 *
 * Copyright 2002 - 2006 JIDE Software Inc. All rights reserved.
 */

package com.element.ui.others;

import com.element.ui.button.JideButton;
import com.element.util.AudioUtil;
import com.element.util.UIUtil;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;

/**
 * 计算器是一个可以进行简单算术计算的组件。由于它扩展了 JPanel，您可以在应用程序的任何地方使用它。
 * <p>
 * 为了使其更加灵活，计算器没有用于显示结果的文本字段。您可以创建自己的 JTextField 或 JLabel 来显示结果。下面是一个创建文本字段并将其与计算器相关联的简单示例。
 * <pre><code>
 * final JTextField textField = new JTextField();
 * textField.setColumns(20);
 * textField.setHorizontalAlignment(JTextField.TRAILING);
 * Calculator calculator = new Calculator();
 * calculator.registerKeyboardActions(textField, JComponent.WHEN_FOCUSED);
 * calculator.addPropertyChangeListener(Calculator.PROPERTY_DISPLAY_TEXT, new PropertyChangeListener() {
 *     public void propertyChange(PropertyChangeEvent evt) {
 *         textField.setText("" + evt.getNewValue());
 *     }
 * });
 * calculator.clear();
 * </code></pre>
 * <p>
 * 使用上面的代码，用户可以直接在文本字段中输入并进行计算。如果您只想显示结果而不介意文本字段是否接受键盘输入，则无需调用 registerKeyboardActions 方法。
 * <p>
 * 所有数字键和运算符键都按预期工作。这里有一些值得一提的特殊键
 * 'C', 'c' 或 ESC 清除当前结果
 * '！'使当前显示的数字由正变负（或由负变正）
 * ENTER 等同于 '='..
 * <p>
 * 使用计算器的另一种有趣方式是不使用 GUI 来使用它。
 * <pre><code>
 * Calculator calculator = new Calculator();
 * calculator.input('1');
 * calculator.input('0');
 * calculator.input('*');
 * calculator.input('2');
 * calculator.input('4');
 * calculator.input('=');
 * System.out.println("10 * 24 = " + calculator.getDisplayText());
 * </code></pre>
 * <p>
 * 打印输出将是“10 * 24 = 240”。
 * <p>
 * 您可以使用多种方法来获取计算器的内部状态。
 * <ul>
 *     <li>getDisplayText() ：获取应显示的结果。请注意，此方法返回一个字符串。</li>
 *     <li>getResult() ：获取上次计算的结果。此方法返回一个双精度值。</li>
 *     <li>getOperator() : 获取当前操作符</li>
 *     <li>isOverflow() ：检查是否有溢出。通常，如果您尝试除以零，就会溢出。</li>
 * </ul>
 */
public class Calculator extends JPanel implements ActionListener {

	private double _result;
	private StringBuffer _op1;
	private StringBuffer _op2;
	private int _operator = OPERATOR_NONE;
	private String _displayText;
	private boolean _overflow = false;
	private boolean _negationOp1 = true;
	private boolean _backspaceOp1 = false;
	private boolean _backspaceOp2 = false;
	private boolean _clearOperatorPending = false;
	private boolean _isFakedEqualPressed = false; // When it is true, it means the focus is lost. So the result should not be updated.
	private boolean _resultCalculated = false;

	public static final int OPERATOR_NONE = -1;
	public static final int OPERATOR_ADD = 0;
	public static final int OPERATOR_MINUS = 1;
	public static final int OPERATOR_MULTIPLY = 2;
	public static final int OPERATOR_DIVIDE = 3;

	private AbstractButton _addButton;
	private AbstractButton _minusButton;
	private AbstractButton _multiplyButton;
	private AbstractButton _divideButton;
	private AbstractButton _pointButton;
	private AbstractButton _equalButton;
	private AbstractButton _backspaceButton;
	private AbstractButton _clearButton;
	private AbstractButton _negativeButton;
	private AbstractButton[] _numberButtons;
	private char _actualCharPoint;

	private NumberFormat _displayFormat;

	public static final char CHAR_CLEAR = 'c';
	public static final char CHAR_POINT = '.';
	public static final char CHAR_ADD = '+';
	public static final char CHAR_MINUS = '-';
	public static final char CHAR_MULTIPLY = '*';
	public static final char CHAR_DIVIDE = '/';
	public static final char CHAR_EQUAL = '=';
	public static final char CHAR_NEGATIVE = '!';
	public static final char CHAR_BACKSPACE = '<';
	public static final char CHAR_0 = '0';
	public static final char CHAR_1 = '1';
	public static final char CHAR_2 = '2';
	public static final char CHAR_3 = '3';
	public static final char CHAR_4 = '4';
	public static final char CHAR_5 = '5';
	public static final char CHAR_6 = '6';
	public static final char CHAR_7 = '7';
	public static final char CHAR_8 = '8';
	public static final char CHAR_9 = '9';

	public static final String PROPERTY_DISPLAY_TEXT = "displayText";
	public static final String PROPERTY_OPERATOR = "operator";

	private int _buttonWidth = 24;
	private int _buttonHeight = 24;
	private int _buttonGap = 2;

	/**
	 * Creates a <code>Calculator</code>.
	 */
	public Calculator() {
		_op1 = new StringBuffer();
		_op2 = new StringBuffer();
		_displayFormat = NumberFormat.getNumberInstance();
		configureNumberFormat();
		initComponents();
		registerKeyboardActions(this, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
	}

	@Override
	public void setLocale(Locale l) {
		unregisterKeyboardActions(this);
		super.setLocale(l);
		_op1 = new StringBuffer();
		_op2 = new StringBuffer();
		_displayFormat = NumberFormat.getNumberInstance(getLocale());
		configureNumberFormat();
		_actualCharPoint = getDisplayFormat().format(2.01).charAt(1);
		_pointButton.setText("" + _actualCharPoint);
		registerKeyboardActions(this, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
	}

	/**
	 * Configures the number format for displaying purpose.
	 */
	protected void configureNumberFormat() {
		_displayFormat.setMaximumFractionDigits(20);
		_displayFormat.setMinimumFractionDigits(0);
		_displayFormat.setGroupingUsed(false);
	}

	/**
	 * Checks if the key event a valid key event that can be accepted by the Calculator.
	 *
	 * @param keyEvent the key event.
	 * @return true if it is a valid key event for the Calculator.
	 */
	public static boolean isValidKeyEvent(KeyEvent keyEvent) {
		char c = keyEvent.getKeyChar();
		return (keyEvent.getModifiersEx() & ~InputEvent.SHIFT_DOWN_MASK) != 0 // if it has any modify, ignore it
				|| Character.isDigit(c) || isOperator(keyEvent)
				|| isEnterOrEqual(keyEvent)
				|| c == KeyEvent.VK_PERIOD || c == KeyEvent.VK_COMMA || c == CHAR_CLEAR || Character.toLowerCase(c) == CHAR_CLEAR
				|| c == KeyEvent.VK_ESCAPE
				|| c == KeyEvent.VK_BACK_SPACE;
	}

	/**
	 * Checks if the key event a key event for operators. In the other words, if it is {@link #CHAR_ADD}, {@link
	 * #CHAR_MINUS}, {@link #CHAR_MULTIPLY} or {@link #CHAR_DIVIDE}, this method will return true.
	 *
	 * @param keyEvent the key event.
	 * @return true if it is a valid key event is an operator.
	 */
	public static boolean isOperator(KeyEvent keyEvent) {
		char c = keyEvent.getKeyChar();
		return c == CHAR_ADD || c == CHAR_MINUS || c == CHAR_MULTIPLY || c == CHAR_DIVIDE;
	}

	/**
	 * Checks if the key event a key event for enter. In the other words, if it is {@link KeyEvent#VK_ENTER} or {@link
	 * KeyEvent#VK_EQUALS}, this method will return true.
	 *
	 * @param keyEvent the key event.
	 * @return true if it is a valid key event is an enter key or an equal key.
	 */
	public static boolean isEnterOrEqual(KeyEvent keyEvent) {
		char c = keyEvent.getKeyChar();
		return c == KeyEvent.VK_ENTER || c == KeyEvent.VK_EQUALS;
	}

	/**
	 * Registers necessary keyboard actions onto the component. Usually the component is a <code>JTextField</code>.
	 *
	 * @param component the component where the key input will be taken and passed to the <code>Calculator</code>.
	 * @param condition the condition as defined in {@link JComponent#registerKeyboardAction(ActionListener,
	 *                  KeyStroke, int)}.
	 */
	public void registerKeyboardActions(JComponent component, int condition) {
		boolean isCellEditor = isCellEditor();
		component.registerKeyboardAction(this, "" + CHAR_ADD, KeyStroke.getKeyStroke(CHAR_ADD), condition);
		component.registerKeyboardAction(this, "" + CHAR_MINUS, KeyStroke.getKeyStroke(CHAR_MINUS), condition);
		component.registerKeyboardAction(this, "" + CHAR_MULTIPLY, KeyStroke.getKeyStroke(CHAR_MULTIPLY), condition);
		component.registerKeyboardAction(this, "" + CHAR_DIVIDE, KeyStroke.getKeyStroke(CHAR_DIVIDE), condition);
		component.registerKeyboardAction(this, "" + CHAR_EQUAL, KeyStroke.getKeyStroke(CHAR_EQUAL), condition);
		if (!isCellEditor)
			component.registerKeyboardAction(this, "" + CHAR_EQUAL, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), condition);
		component.registerKeyboardAction(this, "" + CHAR_0, KeyStroke.getKeyStroke(CHAR_0), condition);
		component.registerKeyboardAction(this, "" + CHAR_1, KeyStroke.getKeyStroke(CHAR_1), condition);
		component.registerKeyboardAction(this, "" + CHAR_2, KeyStroke.getKeyStroke(CHAR_2), condition);
		component.registerKeyboardAction(this, "" + CHAR_3, KeyStroke.getKeyStroke(CHAR_3), condition);
		component.registerKeyboardAction(this, "" + CHAR_4, KeyStroke.getKeyStroke(CHAR_4), condition);
		component.registerKeyboardAction(this, "" + CHAR_5, KeyStroke.getKeyStroke(CHAR_5), condition);
		component.registerKeyboardAction(this, "" + CHAR_6, KeyStroke.getKeyStroke(CHAR_6), condition);
		component.registerKeyboardAction(this, "" + CHAR_7, KeyStroke.getKeyStroke(CHAR_7), condition);
		component.registerKeyboardAction(this, "" + CHAR_8, KeyStroke.getKeyStroke(CHAR_8), condition);
		component.registerKeyboardAction(this, "" + CHAR_9, KeyStroke.getKeyStroke(CHAR_9), condition);
		component.registerKeyboardAction(this, "" + _actualCharPoint, KeyStroke.getKeyStroke(_actualCharPoint), condition);
		component.registerKeyboardAction(this, "" + CHAR_BACKSPACE, KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), condition);
		if (!isCellEditor)
			component.registerKeyboardAction(this, "" + CHAR_CLEAR, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), condition);
		if (!isCellEditor)
			component.registerKeyboardAction(this, "" + CHAR_CLEAR, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
		component.registerKeyboardAction(this, "" + CHAR_CLEAR, KeyStroke.getKeyStroke(Character.toUpperCase(CHAR_CLEAR)), condition);
		component.registerKeyboardAction(this, "" + CHAR_CLEAR, KeyStroke.getKeyStroke(Character.toLowerCase(CHAR_CLEAR)), condition);
	}

	/**
	 * Unregisters the keyboard actions you registered using {@link #registerKeyboardActions(JComponent,
	 * int)}.
	 *
	 * @param component the component.
	 */
	public void unregisterKeyboardActions(JComponent component) {
		boolean isCellEditor = isCellEditor();
		component.unregisterKeyboardAction(KeyStroke.getKeyStroke(CHAR_ADD));
		component.unregisterKeyboardAction(KeyStroke.getKeyStroke(CHAR_MINUS));
		component.unregisterKeyboardAction(KeyStroke.getKeyStroke(CHAR_MULTIPLY));
		component.unregisterKeyboardAction(KeyStroke.getKeyStroke(CHAR_DIVIDE));
		component.unregisterKeyboardAction(KeyStroke.getKeyStroke(CHAR_EQUAL));
		if (!isCellEditor) component.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0));
		component.unregisterKeyboardAction(KeyStroke.getKeyStroke(CHAR_0));
		component.unregisterKeyboardAction(KeyStroke.getKeyStroke(CHAR_1));
		component.unregisterKeyboardAction(KeyStroke.getKeyStroke(CHAR_2));
		component.unregisterKeyboardAction(KeyStroke.getKeyStroke(CHAR_3));
		component.unregisterKeyboardAction(KeyStroke.getKeyStroke(CHAR_4));
		component.unregisterKeyboardAction(KeyStroke.getKeyStroke(CHAR_5));
		component.unregisterKeyboardAction(KeyStroke.getKeyStroke(CHAR_6));
		component.unregisterKeyboardAction(KeyStroke.getKeyStroke(CHAR_7));
		component.unregisterKeyboardAction(KeyStroke.getKeyStroke(CHAR_8));
		component.unregisterKeyboardAction(KeyStroke.getKeyStroke(CHAR_9));
		component.unregisterKeyboardAction(KeyStroke.getKeyStroke(_actualCharPoint));
		component.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0));
		if (!isCellEditor) component.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0));
		component.unregisterKeyboardAction(KeyStroke.getKeyStroke(Character.toUpperCase(CHAR_CLEAR)));
		component.unregisterKeyboardAction(KeyStroke.getKeyStroke(Character.toLowerCase(CHAR_CLEAR)));
	}

	protected void initComponents() {
		setLayout(new CalculatorLayoutManager());
		add(_addButton = createButton("+"));
		add(_minusButton = createButton("-"));
		add(_multiplyButton = createButton("*"));
		add(_divideButton = createButton("/"));
		_numberButtons = new AbstractButton[10];
		for (int i = 0; i <= 9; i++) {
			add(_numberButtons[i] = createButton("" + i));
		}
		_actualCharPoint = getDisplayFormat().format(2.01).charAt(1);
		add(_pointButton = createButton("" + _actualCharPoint));
		add(_equalButton = createButton("="));
		add(_backspaceButton = createButton(null, new BackspaceIcon()));
		add(_negativeButton = createButton(null, new ToggleNegativeIcon()));
		add(_clearButton = createButton("C"));
	}

	/**
	 * Get the flag indicating if the result was calculated at least once.
	 *
	 * @return true if the result was calculated at least once. Otherwise false.
	 */
	public boolean isResultCalculated() {
		return _resultCalculated;
	}

	/**
	 * Set the flag indicating if the result was calculated at least once.
	 *
	 * @param resultCalculated the flag
	 */
	public void setResultCalculated(boolean resultCalculated) {
		_resultCalculated = resultCalculated;
	}

	static class BackspaceIcon implements Icon {
		public BackspaceIcon() {
		}

		public void paintIcon(Component c, Graphics g, int x, int y) {
			Object save = UIUtil.setupShapeAntialiasing(g);
			Color old = g.getColor();
			g.setColor(c.getForeground());
			g.drawLine(x, y + 3, x + 3, y);
			g.drawLine(x, y + 3, x + 3, y + 6);
			g.drawLine(x + 3, y + 3, x + 7, y + 3);
			g.setColor(old);
			UIUtil.restoreShapeAntialiasing(g, save);
		}

		public int getIconWidth() {
			return 7;
		}

		public int getIconHeight() {
			return 7;
		}
	}

	static class ToggleNegativeIcon implements Icon {
		public ToggleNegativeIcon() {
		}

		public void paintIcon(Component c, Graphics g, int x, int y) {
			Color old = g.getColor();
			Object save = UIUtil.setupShapeAntialiasing(g);
			g.setColor(c.getForeground());
			g.drawLine(x, y + 2, x + 6, y + 2);
			g.drawLine(x, y + 7, x + 6, y + 7);
			g.drawLine(x + 3, y, x + 3, y + 5);
			g.setColor(old);
			UIUtil.restoreShapeAntialiasing(g, save);
		}

		public int getIconWidth() {
			return 7;
		}

		public int getIconHeight() {
			return 7;
		}
	}

	/**
	 * Creates the button that is used in the Calculator. By default, it will create a JideButton. Here is the code. You
	 * can override it to create your own button. This method is used to create all buttons except the backspace and the
	 * +/- button. So if you want to override it, it's better to override {@link #createButton(String,
	 * Icon)} method.
	 *
	 * @param text the text on the button.
	 * @return the button.
	 */
	protected AbstractButton createButton(String text) {
		return createButton(text, null);
	}

	/**
	 * Creates the button that is used in the Calculator. By default, it will create a JideButton. Here is the code. You
	 * can override it to create your own button.
	 * <pre><code>
	 * AbstractButton button = new JideButton(text, icon);
	 * button.setOpaque(true);
	 * button.setContentAreaFilled(true);
	 * button.setRequestFocusEnabled(false);
	 * button.setFocusable(false);
	 * button.addActionListener(this);
	 * return button;
	 * </code></pre>
	 *
	 * @param text the text on the button.
	 * @param icon the icon on the button.
	 * @return the button.
	 */
	protected AbstractButton createButton(String text, Icon icon) {
		AbstractButton button = new JideButton(text, icon);
		button.setOpaque(true);
		button.setContentAreaFilled(true);
		button.setRequestFocusEnabled(false);
		button.setFocusable(false);
		button.addActionListener(this);
		return button;
	}

	/**
	 * Checks if the calculator is in overflow state.
	 *
	 * @return true if overflow.
	 */
	public boolean isOverflow() {
		return _overflow;
	}

	/**
	 * Sets the overflow flag.
	 *
	 * @param overflow the overflow flag.
	 */
	public void setOverflow(boolean overflow) {
		_overflow = overflow;
	}

	/**
	 * Inputs a char to the calculator. Please note, not all chars are acceptable. Valid chars are defined in {@link
	 * Calculator} class as CHAR_XXX constants.
	 *
	 * @param c the char input char.
	 */
	public void input(char c) {
		if (CHAR_CLEAR == Character.toLowerCase(c) || CHAR_CLEAR == Character.toUpperCase(c)) {
			clear();
			return;
		}

		if (_overflow) {
			beep();
			return;
		}

		if (Character.isDigit(c) || _actualCharPoint == c) {
			if (_clearOperatorPending) {
				setOperator(OPERATOR_NONE);
				_op1.setLength(0);
				_clearOperatorPending = false;
			}
			if (getOperator() == OPERATOR_NONE) {
				if (_actualCharPoint != c || _op1.indexOf("" + _actualCharPoint) == -1) {
					_op1.append(c);
					_backspaceOp1 = true;
					_backspaceOp2 = false;
					setDisplayText(_op1.toString());
				} else {
					beep();
				}
			} else {
				if (_actualCharPoint != c || _op2.indexOf("" + _actualCharPoint) == -1) {
					_op2.append(c);
					_backspaceOp2 = true;
					_backspaceOp1 = false;
					setDisplayText(_op2.toString());
				} else {
					beep();
				}
			}
		} else {
			switch (c) {
				case CHAR_ADD:
					operatorPressed(OPERATOR_ADD);
					break;
				case CHAR_MINUS:
					operatorPressed(OPERATOR_MINUS);
					break;
				case CHAR_MULTIPLY:
					operatorPressed(OPERATOR_MULTIPLY);
					break;
				case CHAR_DIVIDE:
					operatorPressed(OPERATOR_DIVIDE);
					break;
				case CHAR_EQUAL:
					calculateResult(true);
					_clearOperatorPending = true;
					break;
				case CHAR_NEGATIVE:
					if (_negationOp1) {
						negativePressed(_op1);
						setDisplayText(_op1.toString());
					} else {
						negativePressed(_op2);
						setDisplayText(_op2.toString());
					}
					break;
				case CHAR_BACKSPACE:
					if (_backspaceOp1) {
						backspacePressed(_op1);
						setDisplayText(_op1.toString());
					} else if (_backspaceOp2) {
						backspacePressed(_op2);
						setDisplayText(_op2.toString());
					} else {
						beep();
					}
					break;
			}
		}
	}

	private void operatorPressed(int operator) {
		if (_op1.length() == 0) { // _op1 does not have input yet, make it 0
			_op1.append("0");
		} else if (getOperator() == OPERATOR_NONE || _clearOperatorPending) { // normal process or just pressed =
			_op2.setLength(0);
			calculateResult(false);
		} else {
			if (_op2.length() == 0) { // two operators input continuously
				beep();
				return;
			} else { // enable 3+3+3
				_isFakedEqualPressed = false;
				calculateResult(true);
				_op1.setLength(0);
				_op1.append(getDisplayFormat().format((Double) _result));
				_op2.setLength(0);
			}
		}
		setOperator(operator);
		_negationOp1 = false;
		_clearOperatorPending = false;
	}

	protected void beep() {
		AudioUtil.notifyUser();
	}

	private void negativePressed(StringBuffer buf) {
		if (buf.length() == 0) {
			return;
		}
		if (buf.charAt(0) == CHAR_MINUS) {
			buf.deleteCharAt(0);
		} else {
			buf.insert(0, CHAR_MINUS);
		}
	}

	private void backspacePressed(StringBuffer buf) {
		if (buf.length() == 0) {
			return;
		}
		buf.deleteCharAt(buf.length() - 1);
	}

	/**
	 * Update the result as if the equal was pressed. This method can be used while Enter key is pressed and you need
	 * keep the calculated result.
	 */
	public void updateResult() {
		_isFakedEqualPressed = false;
		calculateResult(true);
	}

	private void calculateResult(boolean equalPressed) {
		if (getOperator() == OPERATOR_NONE) {
			return;
		}

		if (_op1.length() == 0) {
			beep();
			return;
		}

		if (equalPressed) {
			if (_op2.length() == 0) {
				_op2.append(_op1);
			}
		} else if (_op2.length() == 0) {
			return;
		}
		double op1;
		double op2;
		try {
			op1 = getDisplayFormat().parse(_op1.toString()).doubleValue();
			op2 = getDisplayFormat().parse(_op2.toString()).doubleValue();
		} catch (ParseException e) {
			op1 = 0.0;
			op2 = 0.0;
		}
		if (!_isFakedEqualPressed) {
			try {
				switch (getOperator()) {
					case OPERATOR_ADD:
						_result = op1 + op2;
						break;
					case OPERATOR_MINUS:
						_result = op1 - op2;
						break;
					case OPERATOR_MULTIPLY:
						_result = op1 * op2;
						break;
					case OPERATOR_DIVIDE:
						if (op2 == 0) {
							_result = Double.NaN;
							_overflow = true;
						} else {
							_result = op1 / op2;
						}
						break;
				}
			} catch (Exception e) {
				_overflow = true;
			}
		}

		if (_overflow) {
			setDisplayText("E");
		} else {
			setResultCalculated(true);
			_op1.setLength(0);
			if (_displayFormat != null) {
				String displayText = _displayFormat.format(_result);
				setDisplayText(displayText);
			} else {
				setDisplayText("" + _result);
			}
			_op1.append(getDisplayText());
			_negationOp1 = true;
			_backspaceOp1 = true;
			_backspaceOp2 = false;
		}
	}

	private void clearOps() {
		setOperator(OPERATOR_NONE);
		_op1.setLength(0);
		_op2.setLength(0);
	}

	/**
	 * Clears the internal state and reset the calculator.
	 */
	public void clear() {
		clearOps();
		_overflow = false;
		_clearOperatorPending = false;
		setDisplayText("0");
	}

	/**
	 * Gets the last calculated result.
	 *
	 * @return the last calculated result.
	 */
	public double getResult() {
		return _result;
	}

	/**
	 * Gets the display text.
	 *
	 * @return the display text.
	 */
	public String getDisplayText() {
		return _displayText;
	}

	/**
	 * Sets the display text and fire property change event on property named {@link #PROPERTY_DISPLAY_TEXT}.
	 *
	 * @param displayText the displayed text.
	 */
	public void setDisplayText(String displayText) {
		String old = _displayText;
		_displayText = displayText;
		firePropertyChange(PROPERTY_DISPLAY_TEXT, old, _displayText);
	}

	/**
	 * Gets the current operator.
	 *
	 * @return the current operator.
	 */
	public int getOperator() {
		return _operator;
	}

	/**
	 * Sets the operator and fire property change event on property named {@link #PROPERTY_OPERATOR}.
	 *
	 * @param operator the operator.
	 */
	public void setOperator(int operator) {
		int old = _operator;
		if (old != operator) {
			_operator = operator;
			firePropertyChange(PROPERTY_OPERATOR, (Integer) old, (Integer) operator);
		}
	}

	private class CalculatorLayoutManager implements LayoutManager {
		public CalculatorLayoutManager() {
		}

		public void addLayoutComponent(String name, Component comp) {

		}

		public void removeLayoutComponent(Component comp) {
		}

		public Dimension preferredLayoutSize(Container parent) {
			return minimumLayoutSize(parent);
		}

		public Dimension minimumLayoutSize(Container parent) {
			return new Dimension(getButtonWidth() * 4 + getButtonGap() * 3, getButtonHeight() * 5 + getButtonGap() * 4);
		}

		public void layoutContainer(Container parent) {
			int x = 0;
			int y = 0;

			int w = getButtonWidth();
			int h = getButtonHeight();
			int gap = getButtonGap();

			_numberButtons[7].setBounds(x, y, w, h);
			x += w + gap;
			_numberButtons[8].setBounds(x, y, w, h);
			x += w + gap;
			_numberButtons[9].setBounds(x, y, w, h);
			x += w + gap;
			_divideButton.setBounds(x, y, w, h);

			x = 0;
			y += h + gap;

			_numberButtons[4].setBounds(x, y, w, h);
			x += w + gap;
			_numberButtons[5].setBounds(x, y, w, h);
			x += w + gap;
			_numberButtons[6].setBounds(x, y, w, h);
			x += w + gap;
			_multiplyButton.setBounds(x, y, w, h);

			x = 0;
			y += h + gap;

			_numberButtons[1].setBounds(x, y, w, h);
			x += w + gap;
			_numberButtons[2].setBounds(x, y, w, h);
			x += w + gap;
			_numberButtons[3].setBounds(x, y, w, h);
			x += w + gap;
			_minusButton.setBounds(x, y, w, h);

			x = 0;
			y += h + gap;

			_numberButtons[0].setBounds(x, y, w, h);
			x += w + gap;
			_pointButton.setBounds(x, y, w, h);
			x += w + gap;
			_negativeButton.setBounds(x, y, w, h);
			x += w + gap;
			_addButton.setBounds(x, y, w, h);

			x = 0;
			y += h + gap;

			_clearButton.setBounds(x, y, w, h);
			x += w + gap;
			_backspaceButton.setBounds(x, y, w, h);
			x += w + gap;
			_equalButton.setBounds(x, y, w * 2 + gap, h);
		}
	}

	public void actionPerformed(ActionEvent e) {
		Object source = e.getSource();
		if (_addButton == source) {
			input(CHAR_ADD);
		} else if (_minusButton == source) {
			input(CHAR_MINUS);
		} else if (_multiplyButton == source) {
			input(CHAR_MULTIPLY);
		} else if (_divideButton == source) {
			input(CHAR_DIVIDE);
		} else if (_equalButton == source) {
			_isFakedEqualPressed = e.getActionCommand() != null && e.getActionCommand().equals("Faked");
			input(CHAR_EQUAL);
		} else if (_pointButton == source) {
			input(_actualCharPoint);
		} else if (_negativeButton == source) {
			input(CHAR_NEGATIVE);
		} else if (_backspaceButton == source) {
			input(CHAR_BACKSPACE);
		} else if (_clearButton == source) {
			input(CHAR_CLEAR);
		} else {
			boolean found = false;
			for (int i = 0; i <= 9; i++) {
				if (_numberButtons[i] == source) {
					input(("" + i).charAt(0));
					found = true;
					break;
				}
			}
			if (!found) {
				if (e.getActionCommand() != null && e.getActionCommand().length() > 0) {
					fakePressButton(e.getActionCommand().charAt(0));
				} else {
					fakePressButton(CHAR_EQUAL);
				}
			}
		}
	}

	/**
	 * Press the button. By default, we will trigger the action directly on this button. However subclass can override
	 * it to call doClick to mimic the user pressing the button.
	 *
	 * @param button the button
	 */
	protected void fakePressButton(AbstractButton button) {
		actionPerformed(new ActionEvent(button, 0, null));
	}

	private void fakePressButton(char c) {
		if (c == _actualCharPoint) {
			fakePressButton(_pointButton);
			return;
		}
		switch (c) {
			case CHAR_CLEAR -> fakePressButton(_clearButton);
			case CHAR_BACKSPACE -> fakePressButton(_backspaceButton);
			case CHAR_EQUAL -> fakePressButton(_equalButton);
			case CHAR_NEGATIVE -> fakePressButton(_negativeButton);
			case CHAR_ADD -> fakePressButton(_addButton);
			case CHAR_MINUS -> fakePressButton(_minusButton);
			case CHAR_MULTIPLY -> fakePressButton(_multiplyButton);
			case CHAR_DIVIDE -> fakePressButton(_divideButton);
			case CHAR_0 -> fakePressButton(_numberButtons[0]);
			case CHAR_1 -> fakePressButton(_numberButtons[1]);
			case CHAR_2 -> fakePressButton(_numberButtons[2]);
			case CHAR_3 -> fakePressButton(_numberButtons[3]);
			case CHAR_4 -> fakePressButton(_numberButtons[4]);
			case CHAR_5 -> fakePressButton(_numberButtons[5]);
			case CHAR_6 -> fakePressButton(_numberButtons[6]);
			case CHAR_7 -> fakePressButton(_numberButtons[7]);
			case CHAR_8 -> fakePressButton(_numberButtons[8]);
			case CHAR_9 -> fakePressButton(_numberButtons[9]);
		}
	}

	/**
	 * Gets the display format for the number.
	 *
	 * @return the display format for the number.
	 */
	public NumberFormat getDisplayFormat() {
		return _displayFormat;
	}

	/**
	 * Sets the display format for the number.
	 *
	 * @param displayFormat the display format.
	 */
	public void setDisplayFormat(NumberFormat displayFormat) {
		_displayFormat = displayFormat;
	}

	/**
	 * Calculates the pending calculation. If the Calculator has both operations and a valid operator, this method will
	 * do the calculation and set the display text and result.
	 */
	public void commit() {
		if (!_clearOperatorPending) {
			actionPerformed(new ActionEvent(_equalButton, 0, "Faked"));
		}
	}

	/**
	 * Gets the button width.
	 *
	 * @return the button width.
	 */
	public int getButtonWidth() {
		return _buttonWidth;
	}

	/**
	 * Sets the button width.
	 *
	 * @param buttonWidth the new button width.
	 */
	public void setButtonWidth(int buttonWidth) {
		_buttonWidth = buttonWidth;
	}

	/**
	 * Gets the button height.
	 *
	 * @return the button height.
	 */
	public int getButtonHeight() {
		return _buttonHeight;
	}

	/**
	 * Sets the button height.
	 *
	 * @param buttonHeight the new button height.
	 */
	public void setButtonHeight(int buttonHeight) {
		_buttonHeight = buttonHeight;
	}

	/**
	 * Gets the gap between buttons. Default is 2.
	 *
	 * @return the gap between buttons.
	 */
	public int getButtonGap() {
		return _buttonGap;
	}

	public void setButtonGap(int buttonGap) {
		_buttonGap = buttonGap;
	}

	/**
	 * If this method return true, ENTER and ESCAPE key will be registered. Otherwise they will not be. The reason we do
	 * so because the two keys are conflicted with keys in JTable.
	 *
	 * @return true or false.
	 */
	protected boolean isCellEditor() {
		return false;
	}

	public void setInitialValue(String value) {
		_op1.setLength(0);
		_op1.append(value);
		_backspaceOp1 = true;
		_backspaceOp2 = false;
		setDisplayText(_op1.toString());
	}
}
